#pragma once
#include <list>
#include <string>

namespace krryn{
namespace math{
	class matrix;
}

namespace scene{
	class scene_graph_node_base;

	//! Every scene_graph_*_visitor should inherit 
	//! from this class and add a pure virtual
	//! visit method that accepts a pointer to the
	//! node that is to be visited. Eg for a
	//! scene_graph_mesh_node would add the method
	//! virtual void accept(scene_graph_mesh_node*) = 0
	class scene_graph_visitor{
	public:
		// this typedef should be overwritten by sub classes
		typedef scene_graph_node_base node_type;

		virtual ~scene_graph_visitor() {}
	};

	class scene_graph_node_base{
	public:
		typedef std::list<scene_graph_node_base*> child_list_t;
		
		scene_graph_node_base(const std::string &a_NodeName);
		scene_graph_node_base(const std::string &a_NodeName, const std::string &a_Id);

		//! Every node is responsible for 
		//! deleting it's children
		virtual ~scene_graph_node_base();

		virtual void init() = 0;

		virtual scene_graph_node_base* create(const std::string &a_Id) = 0;

		//! Accept a visitor for this node
		virtual void accept(scene_graph_visitor *a_Visitor) = 0;

		//! Update this node
		virtual void update(float a_DeltaTime) = 0;

		//! Update the world matrix of this node
		void update_world_matrix();

		//! Adds a child to this node
		void add_child(scene_graph_node_base *a_Node);

		std::string get_id(){return m_Id;}
		std::string get_node_name(){return m_NodeName;}

		//! Coordinates in local space, 
		//! if an object has no parent local
		//! space is world space.
		math::matrix &m_LocalMatrix;

		//! Coordinates in world space,
		//! recursively derived form the 
		//! parents matrix
		math::matrix &m_WorldMatrix;

		//! Pointer to the parent node
		scene_graph_node_base *m_Parent;
		
		//! List of child nodes
		child_list_t m_Children;

		//! Nodes won't get traversed if they are disabled
		bool m_Enabled;

		//! Simple flag if this node is visible
		bool m_Visible;

	private:
		std::string m_Id;
		const std::string m_NodeName;
	};

	//! Simple scene graph class
	template<typename _Tv>
	class scene_graph_node : public scene_graph_node_base{
	public:
		typedef _Tv visitor_type;

		scene_graph_node(const std::string &a_NodeName) 
			: scene_graph_node_base(a_NodeName) {}
		scene_graph_node(const std::string &a_Id, const std::string &a_NodeName) 
			: scene_graph_node_base(a_Id, a_NodeName) {}

		//! Accept a visitor for this node
		virtual void accept(scene_graph_visitor *a_Visitor){
			// if we can cast this visitor to a concrete visitor (_Tv)

			if(visitor_type *l_Visitor = dynamic_cast<visitor_type*>(a_Visitor)){
				// and if this node corresponds to the node 
				// accepted by the concrete visitor

				typedef typename _Tv::node_type node_type;
				if(node_type *l_This = dynamic_cast<node_type*>(this)){
					// then visit the node
					l_Visitor->visit(l_This);
				}
			}
		}
	};

	class scene_graph{
	public:
		scene_graph() : m_Root(0) {}

		// load a scene graph from the description file
		static scene_graph *create(const std::string &a_Filename);

		// set the root node of the scene graph
		void set_root(scene_graph_node_base *a_Root){m_Root = a_Root;}

		// visit all the scene graph nodes
		// this allows other objects to do something with the node
		void visit(scene_graph_node_base *a_Node, scene_graph_visitor *a_Visitor);
		void visit_all(scene_graph_visitor *a_Visitor){visit(m_Root, a_Visitor);}

		// update all the scene graph nodes
		// this calls their update() method and updates their world matrix
		void update(scene_graph_node_base *a_Node, float a_DeltaTime);
		void update_all(float a_DeltaTime){update(m_Root, a_DeltaTime);}

	private:
		scene_graph_node_base *m_Root;
	};
}
}
